﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace BackgroundTaskLib
{
    public class BackgroundTask
    {
        protected const string DefaultTaskName = "Background Task";

        public const string SystemTaskTag = "System";

        public readonly Guid Id = Guid.NewGuid();

        protected BackgroundTask(string name = DefaultTaskName, bool taskOnUiThread = false, bool callbackOnUiThread = false, int delay = 0, string tag = null)
        {
            Name = name;
            TaskOnUiThread = taskOnUiThread;
            CallbackOnUiThread = callbackOnUiThread;
            Delay = delay;
            Tag = tag;
        }

        public BackgroundTask(Action<Func<double, string, bool>> action, string name = DefaultTaskName, bool taskOnUiThread = false, Action callback = null, bool callbackOnUiThread = false, int delay = 0, string tag = null)
            : this(name, taskOnUiThread, callbackOnUiThread, delay, tag)
        {
            Action = action; 
            Callback = callback;
        }

        public BackgroundTask(Action action, string name = DefaultTaskName, bool taskOnUiThread = false, Action callback = null, bool callbackOnUiThread = false, int delay = 0, string tag = null)
            : this(_ => action(), name, taskOnUiThread, callback, callbackOnUiThread, delay, tag)
        {
        }

        public string Name { get; protected set; }
        public TaskStatus Status { get; protected set; }
        public Task Task { get; protected set; }
        public double Progress { get; protected set; }
        public bool IsIndeterminate { get; protected set; }
        public Exception TaskException { get; protected set; }
        public string Tag { get; private set; }

        private Action<Func<double, string, bool>> Action { get; set; }
        protected bool TaskOnUiThread { get; private set; }
        private Action Callback { get; set; }
        protected bool CallbackOnUiThread { get; private set; }
        protected int Delay { get; private set; }

        protected CancellationTokenSource CancelToken { get; set; }
        protected TaskManager TaskManager { get; set; }

        protected TaskScheduler UIScheduler { get { return TaskManager.UIScheduler; } }

        private Task CreateTask(Action<Func<double, string, bool>> action, Action callback, bool callbackOnUiThread)
        {
            var task = new Task(() =>
            {
                try
                {
                    UpdateStatus(TaskStatus.Running);

                    action(CreateProgressUpdateTask);
                }
                catch (Exception e)
                {
                    HandleException(e);
                }
                finally
                {
                    if (callback == null)
                    {
                        FinishTask(CancelToken.IsCancellationRequested == false);
                    }
                }
            }, CancelToken.Token, TaskCreationOptions.None);

            if (callback != null)
            {
                task.ContinueWith(_ =>
                {
                    try
                    {
                        UpdateStatus(TaskStatus.Callback);

                        callback();
                    }
                    catch (Exception e)
                    {
                        HandleException(e);
                    }
                    finally
                    {
                        FinishTask(CancelToken.IsCancellationRequested == false);
                    }
                }, CancelToken.Token, TaskContinuationOptions.OnlyOnRanToCompletion, callbackOnUiThread ? UIScheduler : TaskScheduler.Default);
            }

            return Delay > 0 ? Task.Delay(Delay).ContinueWith(_ => task.Start(TaskOnUiThread ? UIScheduler : TaskScheduler.Default)) : task;
        }

        protected bool CreateProgressUpdateTask(double progress, string name)
        {
            Task.Factory.StartNew(() =>
            {
                if (progress >= 0 && progress <= 1)
                {
                    IsIndeterminate = false;
                    Progress = progress;
                }
                else
                {
                    IsIndeterminate = true;
                }

                if (string.IsNullOrEmpty(name) == false)
                {
                    Name = name;
                }
            }, CancelToken.Token, TaskCreationOptions.None, UIScheduler);

            return CancelToken.IsCancellationRequested;
        }

        protected void FinishTask(bool isCompleted)
        {
            Debug.WriteLine("FinishTask: {0} ({1})", Name, isCompleted);

            if (Status != TaskStatus.Exception)
            {
                if (isCompleted || IsIndeterminate)
                {
                    Progress = 1;
                }

                IsIndeterminate = false;

                UpdateStatus(isCompleted ? TaskStatus.Completed : TaskStatus.Cancelled);
            }
        }

        protected void HandleException(Exception e)
        {
            Debug.WriteLine("HandleException: {0}: {1}", Name, e);

            TaskException = e;
            UpdateStatus(TaskStatus.Exception);
            Progress = 1;

            CancelToken.Cancel();
        }

        protected void UpdateStatus(TaskStatus status)
        {
            if (Status != status)
            {
                Debug.WriteLine("{0}: {1} -> {2}", Name, Status, status);

                Status = status;
            }
        }

        public void Run()
        {
            if (Delay == 0)
            {
                UpdateStatus(TaskStatus.Starting);

                Task.Start(TaskOnUiThread ? UIScheduler : TaskScheduler.Default);
            }
        }

        public void Cancel()
        {
            UpdateStatus(TaskStatus.Cancelling);

            CancelToken.Cancel();
        }

        public void Wait()
        {
            Task.Wait();
        }

        public virtual void Queued(TaskManager taskManager)
        {
            TaskManager = taskManager;
            CancelToken = new CancellationTokenSource();

            Task = CreateTask(Action, Callback, CallbackOnUiThread);

            UpdateStatus(TaskStatus.Queued);
        }
    }

    public class BackgroundTask<T> : BackgroundTask
    {
        public BackgroundTask(Func<Func<double, string, bool>, T> action, string name = DefaultTaskName, bool taskOnUiThread = false, Action<T> callback = null, bool callbackOnUiThread = false, int delay = 0)
            : base(name, taskOnUiThread, callbackOnUiThread, delay)
        {
            Action = action;
            Callback = callback;
        }

        public BackgroundTask(Func<T> action, string name = DefaultTaskName, bool taskOnUiThread = false, Action<T> callback = null, bool callbackOnUiThread = false, int delay = 0)
            : this(_ => action(), name, taskOnUiThread, callback, callbackOnUiThread, delay)
        {
        }

        private Func<Func<double, string, bool>, T> Action { get; set; }
        private Action<T> Callback { get; set; }

        private Task CreateTask<T>(Func<Func<double, string, bool>, T> action, Action<T> callback, bool callbackOnUiThread)
        {
            var task = new Task<T>(() =>
            {
                var result = default(T);

                try
                {
                    UpdateStatus(TaskStatus.Running);

                    result = action(CreateProgressUpdateTask);
                }
                catch (Exception e)
                {
                    HandleException(e);
                }

                return result;
            }, CancelToken.Token, TaskCreationOptions.None);

            Task lastTask = task;
            if (callback != null)
            {
                lastTask = task.ContinueWith(x =>
                {
                    try
                    {
                        UpdateStatus(TaskStatus.Callback);

                        callback(x.Result);
                    }
                    catch (Exception e)
                    {
                        HandleException(e);
                    }
                    finally
                    {
                        FinishTask(CancelToken.IsCancellationRequested == false);
                    }
                }, CancelToken.Token, TaskContinuationOptions.OnlyOnRanToCompletion, callbackOnUiThread ? UIScheduler : TaskScheduler.Default);
            }

            return Delay > 0 ? Task.Delay(Delay).ContinueWith(_ => task.Start(TaskOnUiThread ? UIScheduler : TaskScheduler.Default)) : task;
        }

        public override void Queued(TaskManager taskManager)
        {
            TaskManager = taskManager;
            CancelToken = new CancellationTokenSource();

            Task = CreateTask(Action, Callback, CallbackOnUiThread);

            UpdateStatus(TaskStatus.Queued);
        }
    }

    public enum TaskStatus
    {
        Created,
        Queued,
        Starting,
        Running,
        Callback,
        Completed,
        Cancelling,
        Cancelled,
        Exception
    }
}
